package pl.edu.agh.uddiProxy.query;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import pl.edu.agh.uddiProxy.dao.DAOHelper;
import pl.edu.agh.uddiProxy.types.ParameterType;
import pl.edu.agh.uddiProxy.types.ParametersMap;
import pl.edu.agh.uddiProxy.types.ServiceDynamicParameters;

public class DistanceFunction {
	private static final String TOKENIZE = "(\\p{javaWhitespace}?\\)*\\p{javaWhitespace}?((and)|(or)|(AND)|(OR))\\p{javaWhitespace}?\\(*\\p{javaWhitespace}?)|(\\p{javaWhitespace}?(\\)|\\()+\\p{javaWhitespace}?)";
	private static final String GREATER = "\\p{javaWhitespace}?>=?\\p{javaWhitespace}?";
	private static final String LOWER = "\\p{javaWhitespace}?<=?\\p{javaWhitespace}?";
	private static final String EQUAL = "\\p{javaWhitespace}?=\\p{javaWhitespace}?";
	
	private String query;
	private Map<String, Double> valueMap;
	private static ParametersMap parametersMap = DAOHelper.getParametersMap();
	private static Pattern tokenPattern = Pattern.compile(TOKENIZE);
	private Pattern greaterPattern = Pattern.compile(GREATER);
	private Pattern lowerPattern = Pattern.compile(LOWER);
	private Pattern equalPattern = Pattern.compile(EQUAL);
	
	private static Logger logger = Logger.getLogger(DistanceFunction.class);
	
	public DistanceFunction(String query, Map<String, Double> valueMap) {
		this.query = query;
		this.valueMap = valueMap;
	}
	
	public DistanceFunction(String query, ServiceDynamicParameters dynamicParameters) {
		this.query = query;
		valueMap = new HashMap<String, Double>();
		for (String key : DAOHelper.getParametersMap().getParametersMap().keySet()) {
			if (parametersMap.getParametersMap().get(key).getType().isNumber()) {
				if (dynamicParameters.getParameterValue(key).getValue() != null && 
						dynamicParameters.getParameterValue(key).getValue().length() > 0) {
					valueMap.put(key, Double.parseDouble(dynamicParameters.getParameterValue(key).getValue()));
				}
			}
		}
	}
	
	public long parse() {
		Map<String, Double> minMap = new HashMap<String, Double>();
		Map<String, Double> maxMap = new HashMap<String, Double>();
		
		String[] tokens = tokenPattern.split(query);
		
		for (String token : tokens) {
			if (token != null && token.length() > 0) {
				if (token.contains(">")) {
					String[] t = greaterPattern.split(token);
					String name = t[0];
					Double d = Double.parseDouble(t[1]);
					Double value = valueMap.get(name);
					if ((minMap.get(name) == null || minMap.get(name) > d) && value != null && d <= value) {
						minMap.put(name, d);
					} 
					
				} else if (token.contains("<")) {
					String[] t = lowerPattern.split(token);
					String name = t[0];
					Double d = Double.parseDouble(t[1]);
					Double value = valueMap.get(name);
					if ((maxMap.get(name) == null || maxMap.get(name) < d) && value != null && d >= value) {
						maxMap.put(name, d);
					} 
					
				} else if (token.contains("=")) {
					String[] t = equalPattern.split(token);
					String name = t[0];
					Double d = Double.parseDouble(t[1]);
					Double value = valueMap.get(name);
					if ((minMap.get(name) == null || minMap.get(name) != d) && value != null && d.equals(value)) {
						maxMap.put(name, d);
						minMap.put(name, d);
					}
				}
			}
		}
		
		Set<String> set = new HashSet<String>();
		set.addAll(maxMap.keySet());
		set.addAll(minMap.keySet());
		double sum = 0.0;
		for (String name : set) {
			if (name != null) {
				sum += normalize(name, valueMap.get(name), minMap.get(name), maxMap.get(name));
			}
		}
		long result = Math.round(Math.sqrt(sum) * 100 / Math.sqrt(set.size()));
		logger.info("distance = " + result);
		
		return result;
	}
	
	private double normalize(String name, Double value, Double userMin, Double userMax) {
		ParameterType type = parametersMap.getParametersMap().get(name);
		Double min = null;
		Double max = null;
		
		if (type.getMinValue() != null) {
			min = Double.parseDouble(type.getMinValue());
		}
		if (type.getMaxValue() != null) {
			max = Double.parseDouble(type.getMaxValue());
		}
		if (userMin != null) {
			min = min == null ? userMin : Math.max(userMin, min);
		}
		if (userMax != null) {
			max = max == null ? userMax : Math.min(userMax, max);
		}
		
		logger.info("normalize " + value + " [" + min + " : " + max + "] " + userMin + " " + userMax + " " + name + " " + type);
		
		if (max == null) {
			max = DAOHelper.getDynamicParameterDAO().getMaxValue(name);
		}
		if (min == null) {
			min = DAOHelper.getDynamicParameterDAO().getMinValue(name);
		}
		
		if (type.isSmallerIsBetter()) {
			value = Math.abs(max - value);			
		} else {
			value = Math.abs(value - min);
		}
		
		double result;
		if (max.equals(min)) {
			result = 1.0;
		} else {
			result = Math.pow(Math.abs(value) / (max - min), 2);
		}
		logger.info(result);
		
		return result;
	}
	
}
